﻿using System.Collections.Generic;
using System.Windows;
using System.Windows.Controls;
using CIAWorldMap;
using OSMLib;

namespace MapSurface
{
    public delegate void DelegateNewViewPort(string sz);

    public class Surface : ISurface, ISurfaceLinear
    {
        private double _farNorth;
        private double _farSouth;
        private double _farEast;
        private double _farWest;

        private Size _availableSize; // used for PNG 
        private Point[] _laosPixelPoly;
        private double _west;
        private double _north;
        private double _east;
        private double _south;
        private double _scale;
        private double _yScale;
        private readonly Canvas _canvas;
        public DelegateNewViewPort _delegateNewViewPort;
        private readonly LaosBorder _border;

        public Surface(Canvas canvas, LaosBorder border)
        {
            _canvas = canvas;
            _border = border;
            ZoomFullOut();
        }

        public Surface(Canvas canvas, LaosBorder border, Size availableSize)
        {
            _canvas = canvas;
            _border = border;
            _availableSize = availableSize;
            ZoomFullOut();
        }

        public bool PointInPolygon(Point p)
        {
            if (_laosPixelPoly.Length < 3)
            {
                return false;
            }

            var oldPoint = new Point(_laosPixelPoly[_laosPixelPoly.Length - 1].X, _laosPixelPoly[_laosPixelPoly.Length - 1].Y);

            bool inside = false;
            Point p1, p2;

            foreach (Point t in _laosPixelPoly)
            {
                var newPoint = new Point(t.X, t.Y);

                if (newPoint.X > oldPoint.X)
                {
                    p1 = oldPoint;
                    p2 = newPoint;
                }
                else
                {
                    p1 = newPoint;
                    p2 = oldPoint;
                }

                if ((newPoint.X < p.X) == (p.X <= oldPoint.X) && ((long)p.Y - (long)p1.Y) * (long)(p2.X - p1.X) < ((long)p2.Y - (long)p1.Y) * (long)(p.X - p1.X))
                {
                    inside = !inside;
                }

                oldPoint = newPoint;
            }
            return inside;
        }

        private void DoSetClipLaos()
        {
           // if (_laosPixelPoly != null)
               // return;

            if (_border._geoPolygon.Count == 0)
                return;

            var pts = new List<Point>();

            int decimate = 0;
            foreach (Coord c in _border._geoPolygon)
            {
                decimate++;
                if (decimate == 50)
                {
                    double px = GetPx(c._lon);
                    double py = GetPy(c._lat);
                    pts.Add(new Point(px, py));
                    decimate = 0;
                }
            }
            _laosPixelPoly = new Point[pts.Count];
            pts.CopyTo(_laosPixelPoly);
        }

        private void DoReport()
        {
            if (_delegateNewViewPort == null)
                return;

            string sz = _north.ToString("0.000") + "N " + _west.ToString("0.000") + "E | " + _south.ToString("0.000") + "N " + _east.ToString("0.000") + "E " +
                _scale.ToString("0.0");

            _delegateNewViewPort(sz);
        }

        public void ZoomFullOut()
        {
            _west = 99.5;
            _north = 22.5;
            _scale = 100;
            _east = _west + Width() / _scale;
            _south = _north - Height() / _scale;
            DoSetClipLaos();
            DoReport();
        }

        private void ZoomIn10(double lat, double lon)
        {
            _scale = _scale * 10;
            _west = _west + 9 * (lon - _west) / 10;
            _north = _north + 9 * (lat - _north) / 10;
            _east = _west + Width() / _scale;
            _south = _north - Height() / _scale;
            DoSetClipLaos();
            DoReport();
        }

        public double GetPx(double lon)
        {
            return (lon - _west) * _scale;
        }

        public double GetPy(double lat)
        {
            double act = (_north - lat);

            if (_yScale == 0.0)
            {
                // may be apply mercator y scaling???
                return act * _scale;
            }

            return act * _yScale;
        }

        public double Height()
        {
            if (_canvas.Parent == null)
            {
                return _availableSize.Height;
            }
            return ((Grid)_canvas.Parent).ActualHeight;
        }

        public double Width()
        {
            if (_canvas.Parent == null)
            {
                return _availableSize.Width;
            }

            return ((Grid)_canvas.Parent).ActualWidth;
        }

        public void ZoomIn10Pixel(Point p)
        {
            double lat = _north - (p.Y / _scale);
            double lon = p.X / _scale + _west;
            ZoomIn10(lat, lon);
        }

        public void ZoomOut(int i)
        {
            double h = Height() / 2;
            double w = Width() / 2;
            _north = _north + h / (_scale * i);
            _west = _west - w / (_scale * i);
            _scale = _scale / i;
            _east = _west + Width() / _scale;
            _south = _north - Height() / _scale;
            DoSetClipLaos();
            DoReport();
        }

        public void SetLimits()
        {
            double dLongitude = Width() / _scale;
            double dLatitude = Height() / _scale;

            _east = _west + dLongitude;
            _south = _north - dLatitude;

            _farNorth = _north + dLatitude;
            _farSouth = _south - dLatitude;
            _farEast = _east + dLongitude;
            _farWest = _west - dLongitude;
        }

        public void Pan(double x, double y)
        {
            double dx = Width() * x / _scale;
            double dy = Height() * y / _scale;
            _west += dx;
            _north += dy;

            double dLongitude = Width() / _scale;
            double dLatitude = Height() / _scale;

            _east = _west + dLongitude;
            _south = _north - dLatitude;

            _farNorth = _north + dLatitude;
            _farSouth = _south - dLatitude;
            _farEast = _east + dLongitude;
            _farWest = _west - dLongitude;

            DoSetClipLaos();
            DoReport();
        }

        public bool WayVisible(OsmWay osmWay)
        {
            if (osmWay._eastLimit < _west)
                return false;

            if (osmWay._southLimit > _north)
                return false;

            if (osmWay._westLimit > _east)
                return false;

            if (osmWay._northLimit < _south)
                return false;

            return true;
        }

        public bool NodeVisible(OsmNode node)
        {
            if (node._wgs.Longitude < _farWest)
                return false;

            if (node._wgs.Latitude > _farNorth)
                return false;

            if (node._wgs.Longitude > _farEast)
                return false;

            if (node._wgs.Latitude < _farSouth)
                return false;

            return true;
        }

        public double Left
        {
            set { _west = value; }
        }

        public double Top
        {
            set { _north = value; }
        }

        public double XScale
        {
            get { return _scale; }
            set { _scale = value; }
        }

        public double YScale
        {
            set { _yScale = value; }
        }

        public Canvas TheCanvas
        {
            get { return _canvas; }
        }
    }
}
